/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2007，所有权利保留。
 * 
 * 项目名：	rumba
 * 文件名：	WeeklyConstraint.java
 * 模块说明：	
 * 修改历史：
 * Nov 10, 2007 - lxm - 创建。
 */
package com.hd123.rumba.sysclock;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.Map;

import net.sf.hibernate.HibernateException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.exception.NoneException;
import com.hd123.rumba.mscb.constraint.IConstraint;
import com.hd123.rumba.runtime.FormatSettings;
import com.hd123.rumba.runtime.backprocess.WeeklySchedule;
import com.hd123.rumba.util.DateUtil;
import com.hd123.rumba.util.XMLUtil;

/**
 * 系统时钟每周重复模式消息订阅约束条件。
 * <p>
 * 
 * 调度公式：从{@link #startDate}到{@link #finishDate}期间，每间隔{@link #every}周{@link validDOWs}指定一周中的某天（Day-of-week，可多选）的指定时间{@link #time}被触发。
 * <p>
 * 
 * 关于周期的计算，把startDate所在周作为第一周，将其后的时间按照every指定的周数分割为若干段，每个段的第一周中指定的一周中的某天的time指定时间将被触发。
 * <p>
 * 
 * 通过{@link #getContext()}序列化格式如下：
 * <p style="border: 1px dashed blue;background-color: gainsboro;">
 * <code>
 &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 &lt;weeklyConstraint<br>
 &nbsp;&nbsp;startDate="2007-11-01 00:00:00"<br>
 &nbsp;&nbsp;finishDate="2007-12-31 00:00:00"<br>
 &nbsp;&nbsp;every="2"<br>
 &nbsp;&nbsp;time="1970-01-01 17:30:00"&gt;<br>
 &nbsp;&nbsp;&lt;validDOW dow="1"/&gt;<br>
 &nbsp;&nbsp;&lt;validDOW dow="7"/&gt;<br>
 &lt;weeklyConstraint/&gt;
 </code>
 * </p>
 * 说明：<code>&lt;validDOW&gt;</code>中的属性<code>dow</code>取值“1”表示星期日，“2”表示星期一，依次类推。
 * <p>
 * 
 * @author lxm
 * 
 */
public class WeeklyConstraint implements IForTaskSchedule, IConstraint {

  private Date startDate = DateUtil.truncate(new Date(), Calendar.DATE);
  private Date finishDate = null;
  private Integer every = new Integer(1);
  private List<Integer> validDOWs = new ArrayList();
  private Date time = null;
  private Map<String, Object> dict = null;

  public String getContext() throws BusinessException {
    Document doc = XMLUtil.newDocument();
    Element eRoot = doc.createElement("weeklyConstraint");
    XMLUtil.writeDate(eRoot, "startDate", startDate);
    XMLUtil.writeDate(eRoot, "finishDate", finishDate);
    XMLUtil.writeInteger(eRoot, "every", every);
    XMLUtil.writeDate(eRoot, "time", time);
    for (Integer dow : validDOWs) {
      Element eValidDOW = doc.createElement("validDOW");
      XMLUtil.writeInteger(eValidDOW, "dow", dow);
      eRoot.appendChild(eValidDOW);
    }
    doc.appendChild(eRoot);
    return XMLUtil.transform(doc);
  }

  public void setContext(String context) throws BusinessException {
    Document doc = XMLUtil.parse(context);
    NodeList nlRoot = doc.getElementsByTagName("weeklyConstraint");
    try {
      if (nlRoot.getLength() == 0 || !(nlRoot.item(0) instanceof Element))
        throw new NoneException();
      Element eRoot = (Element) nlRoot.item(0);
      this.startDate = XMLUtil.readDate(eRoot, "startDate");
      this.finishDate = XMLUtil.readDate(eRoot, "finishDate");
      this.every = XMLUtil.readInteger(eRoot, "every");
      this.time = XMLUtil.readDate(eRoot, "time");

      this.validDOWs.clear();
      NodeList nlValidDOW = eRoot.getElementsByTagName("validDOW");
      for (int i = 0; i < nlValidDOW.getLength(); i++) {
        if (!(nlValidDOW.item(i) instanceof Element))
          continue;
        Element eValidDOW = (Element) nlValidDOW.item(i);
        Integer dow = XMLUtil.readInteger(eValidDOW, "dow");
        this.validDOWs.add(dow);
      }
    } catch (NoneException e) {
      throw new BusinessException("无法识别的约束条件上下文(ConstraintContext)。");
    } catch (ParseException e) {
      throw new BusinessException("无法识别的约束条件上下文(ConstraintContext)。", e);
    }
  }

  public Map<String, Object> getDict() throws BusinessException {
    return dict;
  }

  public void setDict(Map<String, Object> dict) throws BusinessException {
    this.dict = dict;
  }

  public boolean isValid() throws BusinessException, HibernateException {
    assert this.startDate != null && this.every != null && this.time != null;
    assert this.dict != null;
    WeeklySchedule schedule = new WeeklySchedule();
    schedule.setStartDate(startDate);
    schedule.setFinishDate(finishDate);
    schedule.setEvery(every.intValue());
    schedule.setTime(time);
    for (int dow = Calendar.SUNDAY; dow <= Calendar.SATURDAY; dow++)
      schedule.setValid(dow, this.validDOWs.contains(new Integer(dow)));
    Date notifyTime = (Date) this.dict.get(CSysclock.DICT_NOTIFYTIME);
    assert notifyTime != null;
    return schedule.isValid(notifyTime);
  }

  private static final String[] CAPTION_DAYOFWEEK = {
      "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };

  public String getDescription() throws BusinessException {
    StringBuffer sb = new StringBuffer();
    sb.append("从");
    sb.append(FormatSettings.formatDatetime(FormatSettings.SDFyMd, this.startDate));
    sb.append("开始");
    if (this.finishDate != null) {
      sb.append("到");
      sb.append(FormatSettings.formatDatetime(FormatSettings.SDFyMd, this.finishDate));
      sb.append("期间");
    }
    if (this.every.intValue() == 1)
      sb.append("每周");
    else {
      sb.append("每");
      sb.append(this.every);
      sb.append("周");
    }
    boolean first = true;
    for (Integer dow : this.validDOWs) {
      if (!first)
        sb.append("、");
      sb.append(CAPTION_DAYOFWEEK[dow.intValue()]);
      first = false;
    }
    sb.append("的");
    sb.append(FormatSettings.formatDatetime(new SimpleDateFormat("HH:mm"), this.time));
    sb.append("。");
    return sb.toString();
  }

  public void clear() throws BusinessException {
    this.dict = null;
    this.startDate = DateUtil.truncate(new Date(), Calendar.DATE);
    this.finishDate = null;
    this.every = new Integer(1);
    this.validDOWs = new ArrayList();
    this.time = null;
  }

  public Date getNextTime(Date baseTime) throws BusinessException {
    WeeklySchedule schedule = new WeeklySchedule();
    schedule.setStartDate(startDate);
    schedule.setFinishDate(finishDate);
    schedule.setEvery(every.intValue());
    schedule.setTime(time);
    for (int dow = Calendar.SUNDAY; dow <= Calendar.SATURDAY; dow++)
      schedule.setValid(dow, this.validDOWs.contains(new Integer(dow)));
    return schedule.getNextTime(baseTime);
  }

  /** 间隔周数。默认为1，&gt;0。 */
  public Integer getEvery() {
    return every;
  }

  /** 间隔周数。默认为1，&gt;0。 */
  public void setEvery(Integer every) throws BusinessException {
    if (every.intValue() <= 0)
      throw new BusinessException("属性every必须为大于等于1的整数。");
    this.every = every;
  }

  /** 结束日期。包括这一天。允许null，表示不指定结束日期。 */
  public Date getFinishDate() {
    return finishDate;
  }

  /** 结束日期。包括这一天。允许null，表示不指定结束日期。 */
  public void setFinishDate(Date finishDate) {
    this.finishDate = DateUtil.truncate(finishDate, Calendar.DATE);
  }

  /** 开始日期 */
  public Date getStartDate() {
    return startDate;
  }

  /** 开始日期 */
  public void setStartDate(Date startDate) {
    this.startDate = DateUtil.truncate(startDate, Calendar.DATE);
  }

  /** 在一天内的时间。仅时间部分有效。 */
  public Date getTime() {
    return time;
  }

  /** 在一天内的时间。仅时间部分有效。 */
  public void setTime(Date time) throws BusinessException {
    if (time == null)
      throw new BusinessException("属性time不允许为null。");
    this.time = DateUtil.getTimePart(time);
  }

  /** 被选中的一周中的某天（Day-of-week）列表。 */
  public List<Integer> getValidDOWs() {
    return validDOWs;
  }

  /** 被选中的一周中的某天（Day-of-week）列表。 */
  public void setValidDOWs(List<Integer> validDOWs) {
    this.validDOWs = validDOWs;
  }

}
